package com.skynet.distributed.lock.redis;

import com.skynet.distributed.lock.DistributedLock;
import com.skynet.distributed.lock.DistributedLockAutoConfiguration;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.DigestUtils;

import java.lang.reflect.Method;
import java.util.Arrays;

@Aspect
@Configuration
@ConditionalOnClass(RedisDistributedLock.class)
@AutoConfigureAfter(DistributedLockAutoConfiguration.class)
@SuppressWarnings("ALL")
public class RedisAspectAutoConfiguration {

    private static final Logger log = LoggerFactory.getLogger(RedisAutoConfiguration.class);

    @Autowired
    @Qualifier(value = "redisDistributedLock")
    private DistributedLock redisDistributedLock;

    @Pointcut("@annotation(com.skynet.distributed.lock.redis.RedisLock)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();

        RedisLock redisLock = method.getAnnotation(RedisLock.class);

        String key = redisLock.value();

        if (StringUtils.isEmpty(key)) {
            Object[] args = joinPoint.getArgs();
            key = DigestUtils.md5DigestAsHex(Arrays.toString(args).getBytes());
        }

        int retryTimes = RedisLock.LockFailAction.CONTINUE.equals(redisLock.action()) ?
                redisLock.retryTimes() : 0;

        boolean lock = redisDistributedLock.lock(key, redisLock.keepMillis(), retryTimes, redisLock.sleepMillis());

        if (!lock) {
            log.debug("get lock failed : {}", key);
            return null;
        }

        //得到锁,执行方法，释放锁
        log.debug("get lock success : {}", key);
        try {
            return joinPoint.proceed();
        } catch (Exception e) {
            log.error("execute locked method occured an exception", e);
        } finally {
            boolean releaseResult = redisDistributedLock.releaseLock(key);
            log.debug("release lock : {} {}", key, (releaseResult ? " success" : " failed"));
        }
        return null;
    }

}
